package com.airport.schedule.genetic;

import com.airport.schedule.Problem;
import com.airport.schedule.ScheduleSolution;
import com.airport.schedule.model.Flight;
import com.airport.schedule.model.FlightGate;
import org.jgap.*;
import org.jgap.impl.CrossoverOperator;
import org.jgap.impl.DefaultConfiguration;
import org.jgap.impl.MapGene;
import org.jgap.impl.MutationOperator;
import org.jgap.Genotype;

import java.util.HashMap;
import java.util.Map;

public class GeneticScheduler extends Thread {

    private Problem problem;
    private ScheduleSolution solution;
    private ScheduleFitnessFunction fitnessFunction;
    private Genotype genotype;

    private int maxIterations;
    private boolean earlyStop;

    public GeneticScheduler(Problem problem, int popSize,
                            double muteRate, double crossRate,
                            int maxIterations, boolean earlyStop) throws InvalidConfigurationException {

        this.problem = problem;
        this.maxIterations = maxIterations;
        this.earlyStop = earlyStop;
        this.fitnessFunction = new ScheduleFitnessFunction(this);

        int numGenes = problem.getScheduleFlights().size();
        int mutationNum = (int)(muteRate * numGenes);
        int crossNum = (int)(crossRate * numGenes);
        Configuration.reset();
        Configuration conf = new DefaultConfiguration();
        Configuration.resetProperty(Configuration.PROPERTY_FITEVAL_INST);
        conf.setFitnessEvaluator(new DeltaFitnessEvaluator()); // 目标值越小越好
        conf.addGeneticOperator(new MutationOperator(conf, mutationNum));
        conf.addGeneticOperator(new CrossoverOperator(conf, crossNum, true, true));
        conf.setPopulationSize(popSize);
        conf.setFitnessFunction(fitnessFunction);
        conf.setSampleChromosome(createSampleChromosomes(conf, numGenes));
        conf.lockSettings();

        this.genotype = Genotype.randomInitialGenotype(conf);
    }

    @Override
    public void run() {
        evolve();
    }

    public ScheduleSolution getSolution() {
        return solution;
    }

    private void evolve() {
        IChromosome bestChromosome = null;
        boolean optimalFound = false;
        int evolutions = 0;
        double fitness = -1;
        int sameCount = 0;
        while(!optimalFound && evolutions < maxIterations){
            genotype.evolve();
            bestChromosome = genotype.getFittestChromosome();
            double fitnessValue = fitnessFunction.getFitnessValue(bestChromosome);
            if(earlyStop) {
                if (Math.abs(fitnessValue - fitness) <= 1e-6) {
                    sameCount++;
                    if (sameCount >= 100) {
                        System.out.println("无法再找到更好结果：" + fitnessValue);
                        break;
                    }
                } else {
                    sameCount = 0;
                    fitness = fitnessValue;
                }
            }
            System.out.println("fitness value = " + fitnessValue + " ");
            if(fitnessValue <= 0){
                System.out.println("找到最优解！");
                optimalFound = true;
            }
            evolutions++;
        }
        this.solution = parse(bestChromosome);
    }

    private IChromosome createSampleChromosomes(Configuration conf, int numGenes)
            throws InvalidConfigurationException {
        Gene[] sampleGenes = new Gene[numGenes];
        for (int i = 0; i < sampleGenes.length; i++){
            Map<String,Integer> alleles = new HashMap<>();
            Flight flight = problem.getFlightAt(i);
            for(int j = 0; j < problem.getScheduleGates().size(); j++) {
                FlightGate gate = problem.getFlightGateAt(j);
                if(gate.acceptFlight(flight)) {
                    alleles.put(gate.getFlightGateId(), j);
                }
            }
            sampleGenes[i] = new MapGene(conf, alleles);
        }
        return new Chromosome(conf, sampleGenes);
    }

    ScheduleSolution parse(IChromosome chromosome) {
        ScheduleSolution solution = new ScheduleSolution(problem);
        Gene[] genes = chromosome.getGenes();
        for(int flightIndex = 0; flightIndex < genes.length; flightIndex++) {
            Gene gene = genes[flightIndex];
            int flightGateIndex = (int)gene.getAllele();
            FlightGate gate = problem.getFlightGateAt(flightGateIndex);
            Flight flight = problem.getFlightAt(flightIndex);
            solution.assign(flight, gate);
        }
        return solution;
    }

}
